home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / lib / firefox-3.5.5 / components / nsMicrosummaryService.js < prev    next >
Text File  |  2009-11-09  |  77KB  |  2,261 lines

  1. //@line 40 "/build/buildd/firefox-3.5-3.5.5+nobinonly/build-tree/mozilla/browser/components/microsummaries/src/nsMicrosummaryService.js"
  2.  
  3. const Cc = Components.classes;
  4. const Ci = Components.interfaces;
  5. const Cr = Components.results;
  6. const Cu = Components.utils;
  7.  
  8. const PERMS_FILE    = 0644;
  9. const MODE_WRONLY   = 0x02;
  10. const MODE_CREATE   = 0x08;
  11. const MODE_TRUNCATE = 0x20;
  12.  
  13. const NS_ERROR_MODULE_DOM = 2152923136;
  14. const NS_ERROR_DOM_BAD_URI = NS_ERROR_MODULE_DOM + 1012;
  15.  
  16. // How often to check for microsummaries that need updating, in milliseconds.
  17. const CHECK_INTERVAL = 15 * 1000; // 15 seconds
  18. // How often to check for generator updates, in seconds
  19. const GENERATOR_INTERVAL = 7 * 86400; // 1 week
  20.  
  21. const MICSUM_NS = "http://www.mozilla.org/microsummaries/0.1";
  22. const XSLT_NS = "http://www.w3.org/1999/XSL/Transform";
  23.  
  24. const ANNO_MICSUM_GEN_URI    = "microsummary/generatorURI";
  25. const ANNO_MICSUM_EXPIRATION = "microsummary/expiration";
  26. const ANNO_STATIC_TITLE      = "bookmarks/staticTitle";
  27. const ANNO_CONTENT_TYPE      = "bookmarks/contentType";
  28.  
  29. const MAX_SUMMARY_LENGTH = 4096;
  30.  
  31. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  32.  
  33. function MicrosummaryService() {
  34.   this._obs.addObserver(this, "xpcom-shutdown", true);
  35.  
  36.   Cc["@mozilla.org/preferences-service;1"].
  37.     getService(Ci.nsIPrefService).
  38.     getBranch("browser.microsummary.").
  39.     QueryInterface(Ci.nsIPrefBranch2).
  40.     addObserver("", this, true);
  41.  
  42.   this._initTimers();
  43.   this._cacheLocalGenerators();
  44. }
  45.  
  46. MicrosummaryService.prototype = {
  47.   // Bookmarks Service
  48.   __bms: null,
  49.   get _bms() {
  50.     if (!this.__bms)
  51.       this.__bms = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
  52.                    getService(Ci.nsINavBookmarksService);
  53.     return this.__bms;
  54.   },
  55.  
  56.   // Annotation Service
  57.   __ans: null,
  58.   get _ans() {
  59.     if (!this.__ans)
  60.       this.__ans = Cc["@mozilla.org/browser/annotation-service;1"].
  61.                    getService(Ci.nsIAnnotationService);
  62.     return this.__ans;
  63.   },
  64.  
  65.   // IO Service
  66.   __ios: null,
  67.   get _ios() {
  68.     if (!this.__ios)
  69.       this.__ios = Cc["@mozilla.org/network/io-service;1"].
  70.                    getService(Ci.nsIIOService);
  71.     return this.__ios;
  72.   },
  73.  
  74.   // Observer Service
  75.   __obs: null,
  76.   get _obs() {
  77.     if (!this.__obs)
  78.       this.__obs = Cc["@mozilla.org/observer-service;1"].
  79.                    getService(Ci.nsIObserverService);
  80.     return this.__obs;
  81.   },
  82.  
  83.   /**
  84.    * Make a URI from a spec.
  85.    * @param   spec
  86.    *          The string spec of the URI.
  87.    * @returns An nsIURI object.
  88.    */
  89.   _uri: function MSS__uri(spec) {
  90.     return this._ios.newURI(spec, null, null);
  91.   },
  92.  
  93.   // Directory Locator
  94.   __dirs: null,
  95.   get _dirs() {
  96.     if (!this.__dirs)
  97.       this.__dirs = Cc["@mozilla.org/file/directory_service;1"].
  98.                     getService(Ci.nsIProperties);
  99.     return this.__dirs;
  100.   },
  101.  
  102.   // The update interval as specified by the user (defaults to 30 minutes)
  103.   get _updateInterval() {
  104.     var updateInterval =
  105.       getPref("browser.microsummary.updateInterval", 30);
  106.     // the minimum update interval is 1 minute
  107.     return Math.max(updateInterval, 1) * 60 * 1000;
  108.   },
  109.  
  110.   // A cache of local microsummary generators.  This gets built on startup
  111.   // by the _cacheLocalGenerators() method.
  112.   _localGenerators: {},
  113.  
  114.   // The timer that periodically checks for microsummaries needing updating.
  115.   _timer: null,
  116.  
  117.   // XPCOM registration
  118.   classDescription: "Microsummary Service",
  119.   contractID: "@mozilla.org/microsummary/service;1",
  120.   classID: Components.ID("{460a9792-b154-4f26-a922-0f653e2c8f91}"),
  121.   QueryInterface: XPCOMUtils.generateQI([Ci.nsIMicrosummaryService, 
  122.                                          Ci.nsISupportsWeakReference,
  123.                                          Ci.nsIObserver]),
  124.  
  125.   // nsIObserver
  126.   observe: function MSS_observe(subject, topic, data) {
  127.     switch (topic) {
  128.       case "xpcom-shutdown":
  129.         this._destroy();
  130.         break;
  131.       case "nsPref:changed":
  132.         if (data == "enabled")
  133.           this._initTimers();
  134.         break;
  135.     }
  136.   },
  137.  
  138.   _initTimers: function MSS__initTimers() {
  139.     if (this._timer)
  140.       this._timer.cancel();
  141.  
  142.     if (!getPref("browser.microsummary.enabled", true))
  143.       return;
  144.  
  145.     // Periodically update microsummaries that need updating.
  146.     this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  147.     var callback = {
  148.       _svc: this,
  149.       notify: function(timer) { this._svc._updateMicrosummaries() }
  150.     };
  151.     this._timer.initWithCallback(callback,
  152.                                  CHECK_INTERVAL,
  153.                                  this._timer.TYPE_REPEATING_SLACK);
  154.  
  155.     // Setup a cross-session timer to periodically check for generator updates.
  156.     var updateManager = Cc["@mozilla.org/updates/timer-manager;1"].
  157.                         getService(Ci.nsIUpdateTimerManager);
  158.     var interval = getPref("browser.microsummary.generatorUpdateInterval",
  159.                            GENERATOR_INTERVAL);
  160.     var updateCallback = {
  161.       _svc: this,
  162.       notify: function(timer) { this._svc._updateGenerators() }
  163.     };
  164.     updateManager.registerTimer("microsummary-generator-update-timer",
  165.                                 updateCallback, interval);
  166.   },
  167.   
  168.   _destroy: function MSS__destroy() {
  169.     this._timer.cancel();
  170.     this._timer = null;
  171.   },
  172.  
  173.   _updateMicrosummaries: function MSS__updateMicrosummaries() {
  174.     var bookmarks = this._getBookmarks();
  175.  
  176.     var now = Date.now();
  177.     var updateInterval = this._updateInterval;
  178.     for ( var i = 0; i < bookmarks.length; i++ ) {
  179.       var bookmarkID = bookmarks[i];
  180.  
  181.       // Skip this page if its microsummary hasn't expired yet.
  182.       if (this._ans.itemHasAnnotation(bookmarkID, ANNO_MICSUM_EXPIRATION) &&
  183.           this._ans.getItemAnnotation(bookmarkID, ANNO_MICSUM_EXPIRATION) > now)
  184.         continue;
  185.  
  186.       // Reset the expiration time immediately, so if the refresh is failing
  187.       // we don't try it every 15 seconds, potentially overloading the server.
  188.       this._setAnnotation(bookmarkID, ANNO_MICSUM_EXPIRATION, now + updateInterval);
  189.  
  190.       // Try to update the microsummary, but trap errors, so an update
  191.       // that throws doesn't prevent us from updating the rest of them.
  192.       try {
  193.         this.refreshMicrosummary(bookmarkID);
  194.       }
  195.       catch(ex) {
  196.         Cu.reportError(ex);
  197.       }
  198.     }
  199.   },
  200.  
  201.   _updateGenerators: function MSS__updateGenerators() {
  202.     var generators = this._localGenerators;
  203.     var update = getPref("browser.microsummary.updateGenerators", true);
  204.     if (!generators || !update)
  205.       return;
  206.  
  207.     for (let uri in generators)
  208.       generators[uri].update();
  209.   },
  210.  
  211.   _updateMicrosummary: function MSS__updateMicrosummary(bookmarkID, microsummary) {
  212.     var title = this._bms.getItemTitle(bookmarkID);
  213.  
  214.     // Ensure the user-given title is cached
  215.     if (!this._ans.itemHasAnnotation(bookmarkID, ANNO_STATIC_TITLE))
  216.       this._setAnnotation(bookmarkID, ANNO_STATIC_TITLE, title);
  217.  
  218.     // A string identifying the bookmark to use when logging the update.
  219.     var bookmarkIdentity = bookmarkID;
  220.  
  221.     // Update if the microsummary differs from the current title.
  222.     if (!title || title != microsummary.content) {
  223.       this._bms.setItemTitle(bookmarkID, microsummary.content);
  224.       var subject = new LiveTitleNotificationSubject(bookmarkID, microsummary);
  225.       LOG("updated live title for " + bookmarkIdentity +
  226.           " from '" + (title == null ? "<no live title>" : title) +
  227.           "' to '" + microsummary.content + "'");
  228.       this._obs.notifyObservers(subject, "microsummary-livetitle-updated", title);
  229.     }
  230.     else {
  231.       LOG("didn't update live title for " + bookmarkIdentity + "; it hasn't changed");
  232.     }
  233.  
  234.     // Whether or not the title itself has changed, we still save any changes
  235.     // to the update interval, since the interval represents how long to wait
  236.     // before checking again for updates, and that can vary across updates,
  237.     // even when the title itself hasn't changed.
  238.     this._setAnnotation(bookmarkID, ANNO_MICSUM_EXPIRATION,
  239.                   Date.now() + (microsummary.updateInterval || this._updateInterval));
  240.   },
  241.  
  242.   /**
  243.    * Load local generators into the cache.
  244.    * 
  245.    */
  246.   _cacheLocalGenerators: function MSS__cacheLocalGenerators() {
  247.     // Load generators from the application directory.
  248.     var appDir = this._dirs.get("MicsumGens", Ci.nsIFile);
  249.     if (appDir.exists())
  250.       this._cacheLocalGeneratorDir(appDir);
  251.  
  252.     // Load generators from the user's profile.
  253.     var profileDir = this._dirs.get("UsrMicsumGens", Ci.nsIFile);
  254.     if (profileDir.exists())
  255.       this._cacheLocalGeneratorDir(profileDir);
  256.   },
  257.  
  258.   /**
  259.    * Load local generators from a directory into the cache.
  260.    *
  261.    * @param   dir
  262.    *          nsIFile object pointing to directory containing generator files
  263.    * 
  264.    */
  265.   _cacheLocalGeneratorDir: function MSS__cacheLocalGeneratorDir(dir) {
  266.     var files = dir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
  267.     var file = files.nextFile;
  268.  
  269.     while (file) {
  270.       // Recursively load generators so support packs containing
  271.       // lots of generators can organize them into multiple directories.
  272.       if (file.isDirectory())
  273.         this._cacheLocalGeneratorDir(file);
  274.       else
  275.         this._cacheLocalGeneratorFile(file);
  276.  
  277.       file = files.nextFile;
  278.     }
  279.     files.close();
  280.   },
  281.  
  282.   /**
  283.    * Load a local generator from a file into the cache.
  284.    * 
  285.    * @param   file
  286.    *          nsIFile object pointing to file from which to load generator
  287.    * 
  288.    */
  289.   _cacheLocalGeneratorFile: function MSS__cacheLocalGeneratorFile(file) {
  290.     var uri = this._ios.newFileURI(file);
  291.  
  292.     var t = this;
  293.     var callback =
  294.       function MSS_cacheLocalGeneratorCallback(resource) {
  295.         try     { t._handleLocalGenerator(resource) }
  296.         finally { resource.destroy() }
  297.       };
  298.  
  299.     var resource = new MicrosummaryResource(uri);
  300.     resource.load(callback);
  301.   },
  302.  
  303.   _handleLocalGenerator: function MSS__handleLocalGenerator(resource) {
  304.     if (!resource.isXML)
  305.       throw(resource.uri.spec + " microsummary generator loaded, but not XML");
  306.  
  307.     var generator = new MicrosummaryGenerator(null, resource.uri);
  308.     generator.initFromXML(resource.content);
  309.  
  310.     // Add the generator to the local generators cache.
  311.     // XXX Figure out why Firefox crashes on shutdown if we index generators
  312.     // by uri.spec but doesn't crash if we index by uri.spec.split().join().
  313.     //this._localGenerators[generator.uri.spec] = generator;
  314.     this._localGenerators[generator.uri.spec.split().join()] = generator;
  315.  
  316.     LOG("loaded local microsummary generator\n" +
  317.         "  file: " + generator.localURI.spec + "\n" +
  318.         "    ID: " + generator.uri.spec);
  319.   },
  320.  
  321.   // nsIMicrosummaryService
  322.  
  323.   /**
  324.    * Return a microsummary generator for the given URI.
  325.    *
  326.    * @param   generatorURI
  327.    *          the URI of the generator
  328.    */
  329.   getGenerator: function MSS_getGenerator(generatorURI) {
  330.     return this._localGenerators[generatorURI.spec] ||
  331.       new MicrosummaryGenerator(generatorURI);
  332.   },
  333.  
  334.   /**
  335.    * Install the microsummary generator from the resource at the supplied URI.
  336.    * Callable by content via the addMicrosummaryGenerator() sidebar method.
  337.    *
  338.    * @param   generatorURI
  339.    *          the URI of the resource providing the generator
  340.    *
  341.    */
  342.   addGenerator: function MSS_addGenerator(generatorURI) {
  343.     var t = this;
  344.     var callback =
  345.       function MSS_addGeneratorCallback(resource) {
  346.         try     { t._handleNewGenerator(resource) }
  347.         finally { resource.destroy() }
  348.       };
  349.  
  350.     var resource = new MicrosummaryResource(generatorURI);
  351.     resource.load(callback);
  352.   },
  353.  
  354.   _handleNewGenerator: function MSS__handleNewGenerator(resource) {
  355.     if (!resource.isXML)
  356.       throw(resource.uri.spec + " microsummary generator loaded, but not XML");
  357.  
  358.     // XXX Make sure it's a valid microsummary generator.
  359.  
  360.     var rootNode = resource.content.documentElement;
  361.  
  362.     // Add a reference to the URI from which we got this generator so we have
  363.     // a unique identifier for the generator and also so we can check back later
  364.     // for updates.
  365.     rootNode.setAttribute("uri", "urn:source:" + resource.uri.spec);
  366.  
  367.     this.installGenerator(resource.content);
  368.   },
  369.  
  370.   /**
  371.    * Install a microsummary generator from the given XML definition.
  372.    *
  373.    * @param   xmlDefinition
  374.    *          an nsIDOMDocument XML document defining the generator
  375.    *
  376.    * @returns the newly-installed nsIMicrosummaryGenerator generator
  377.    *
  378.    */
  379.   installGenerator: function MSS_installGenerator(xmlDefinition) {
  380.     var rootNode = xmlDefinition.getElementsByTagNameNS(MICSUM_NS, "generator")[0];
  381.  
  382.     var generatorID = rootNode.getAttribute("uri");
  383.  
  384.     // The existing cache entry for this generator, if it is already installed.
  385.     var generator = this._localGenerators[generatorID];
  386.  
  387.     var topic;
  388.     if (generator)
  389.       topic = "microsummary-generator-updated";
  390.     else {
  391.       // This generator is not already installed.  Save it as a new file.
  392.       topic = "microsummary-generator-installed";
  393.       var generatorName = rootNode.getAttribute("name");
  394.       var fileName = sanitizeName(generatorName) + ".xml";
  395.       var file = this._dirs.get("UsrMicsumGens", Ci.nsIFile);
  396.       file.append(fileName);
  397.       file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
  398.       generator = new MicrosummaryGenerator(null, this._ios.newFileURI(file));
  399.       this._localGenerators[generatorID] = generator;
  400.     }
  401.  
  402.     // Initialize (or reinitialize) the generator from its XML definition,
  403.     // the save the definition to the generator's file.
  404.     generator.initFromXML(xmlDefinition);
  405.     generator.saveXMLToFile(xmlDefinition);
  406.  
  407.     LOG("installed generator " + generatorID);
  408.  
  409.     this._obs.notifyObservers(generator, topic, null);
  410.  
  411.     return generator;
  412.   },
  413.  
  414.   /**
  415.    * Get the set of microsummaries available for a given page.  The set
  416.    * might change after this method returns, since this method will trigger
  417.    * an asynchronous load of the page in question (if it isn't already loaded)
  418.    * to see if it references any page-specific microsummaries.
  419.    *
  420.    * If the caller passes a bookmark ID, and one of the microsummaries
  421.    * is the current one for the bookmark, this method will retrieve content
  422.    * from the datastore for that microsummary, which is useful when callers
  423.    * want to display a list of microsummaries for a page that isn't loaded,
  424.    * and they want to display the actual content of the selected microsummary
  425.    * immediately (rather than after the content is asynchronously loaded).
  426.    *
  427.    * @param   pageURI
  428.    *          the URI of the page for which to retrieve available microsummaries
  429.    *
  430.    * @param   bookmarkID (optional)
  431.    *          the ID of the bookmark for which this method is being called
  432.    *
  433.    * @returns an nsIMicrosummarySet of nsIMicrosummaries for the given page
  434.    *
  435.    */
  436.   getMicrosummaries: function MSS_getMicrosummaries(pageURI, bookmarkID) {
  437.     var microsummaries = new MicrosummarySet();
  438.  
  439.     if (!getPref("browser.microsummary.enabled", true))
  440.       return microsummaries;
  441.  
  442.     // Get microsummaries defined by local generators.
  443.     for (var genURISpec in this._localGenerators) {
  444.       var generator = this._localGenerators[genURISpec];
  445.  
  446.       if (generator.appliesToURI(pageURI)) {
  447.         var microsummary = new Microsummary(pageURI, generator);
  448.  
  449.         // If this is the current microsummary for this bookmark, load the content
  450.         // from the datastore so it shows up immediately in microsummary picking UI.
  451.         if (bookmarkID != -1 && this.isMicrosummary(bookmarkID, microsummary))
  452.           microsummary._content = this._bms.getItemTitle(bookmarkID);
  453.  
  454.         microsummaries.AppendElement(microsummary);
  455.       }
  456.     }
  457.  
  458.     // If a bookmark identifier has been provided, list its microsummary
  459.     // synchronously, if any.
  460.     if (bookmarkID != -1 && this.hasMicrosummary(bookmarkID)) {
  461.       var currentMicrosummary = this.getMicrosummary(bookmarkID);
  462.       if (!microsummaries.hasItemForMicrosummary(currentMicrosummary))
  463.         microsummaries.AppendElement(currentMicrosummary);
  464.     }
  465.  
  466.     // Get microsummaries defined by the page.  If we don't have the page,
  467.     // download it asynchronously, and then finish populating the set.
  468.     var resource = getLoadedMicrosummaryResource(pageURI);
  469.     if (resource) {
  470.       try     { microsummaries.extractFromPage(resource) }
  471.       finally { resource.destroy() }
  472.     }
  473.     else {
  474.       // Load the page with a callback that will add the page's microsummaries
  475.       // to the set once the page has loaded.
  476.       var callback = function MSS_extractFromPageCallback(resource) {
  477.         try     { microsummaries.extractFromPage(resource) }
  478.         finally { resource.destroy() }
  479.       };
  480.  
  481.       try {
  482.         resource = new MicrosummaryResource(pageURI);
  483.         resource.load(callback);
  484.       }
  485.       catch(e) {
  486.         // We don't have to do anything special if the call fails besides
  487.         // destroying the Resource object.  We can just return the list
  488.         // of microsummaries without including page-defined microsummaries.
  489.         if (resource)
  490.           resource.destroy();
  491.         LOG("error downloading page to extract its microsummaries: " + e);
  492.       }
  493.     }
  494.  
  495.     return microsummaries;
  496.   },
  497.  
  498.   /**
  499.    * Change all occurrences of a specific value in a given field to a new value.
  500.    *
  501.    * @param   fieldName
  502.    *          the name of the field whose values should be changed
  503.    * @param   oldValue
  504.    *          the value that should be changed
  505.    * @param   newValue
  506.    *          the value to which it should be changed
  507.    *
  508.    */
  509.   _changeField: function MSS__changeField(fieldName, oldValue, newValue) {
  510.     var bookmarks = this._getBookmarks();
  511.  
  512.     for ( var i = 0; i < bookmarks.length; i++ ) {
  513.       var bookmarkID = bookmarks[i];
  514.  
  515.       if (this._ans.itemHasAnnotation(bookmarkID, fieldName) &&
  516.           this._ans.getItemAnnotation(bookmarkID, fieldName) == oldValue)
  517.         this._setAnnotation(bookmarkID, fieldName, newValue);
  518.     }
  519.   },
  520.  
  521.   /**
  522.    * Get the set of bookmarks with microsummaries.
  523.    *
  524.    * This is the internal version of this method, which is not accessible
  525.    * via XPCOM but is more performant; inside this component, use this version.
  526.    * Outside the component, use getBookmarks (no underscore prefix) instead.
  527.    *
  528.    * @returns an array of place: uris representing bookmarks items
  529.    *
  530.    */
  531.   _getBookmarks: function MSS__getBookmarks() {
  532.     var bookmarks;
  533.  
  534.     // This try/catch block is a temporary workaround for bug 336194.
  535.     try {
  536.       bookmarks = this._ans.getItemsWithAnnotation(ANNO_MICSUM_GEN_URI, {});
  537.     }
  538.     catch(e) {
  539.       bookmarks = [];
  540.     }
  541.  
  542.     return bookmarks;
  543.   },
  544.  
  545.   _setAnnotation: function MSS__setAnnotation(aBookmarkId, aFieldName, aFieldValue) {
  546.     this._ans.setItemAnnotation(aBookmarkId,
  547.                                 aFieldName,
  548.                                 aFieldValue,
  549.                                 0,
  550.                                 this._ans.EXPIRE_NEVER);
  551.   },
  552.  
  553.   /**
  554.    * Get the set of bookmarks with microsummaries.
  555.    *
  556.    * This is the external version of this method and is accessible via XPCOM.
  557.    * Use it outside this component. Inside the component, use _getBookmarks
  558.    * (with underscore prefix) instead for performance.
  559.    *
  560.    * @returns an nsISimpleEnumerator enumeration of bookmark IDs
  561.    *
  562.    */
  563.   getBookmarks: function MSS_getBookmarks() {
  564.     return new ArrayEnumerator(this._getBookmarks());
  565.   },
  566.  
  567.   /**
  568.    * Get the current microsummary for the given bookmark.
  569.    *
  570.    * @param   bookmarkID
  571.    *          the bookmark for which to get the current microsummary
  572.    *
  573.    * @returns the current microsummary for the bookmark, or null
  574.    *          if the bookmark does not have a current microsummary
  575.    *
  576.    */
  577.   getMicrosummary: function MSS_getMicrosummary(bookmarkID) {
  578.     if (!this.hasMicrosummary(bookmarkID))
  579.       return null;
  580.  
  581.     var pageURI = this._bms.getBookmarkURI(bookmarkID);
  582.     var generatorURI = this._uri(this._ans.getItemAnnotation(bookmarkID,
  583.                                                              ANNO_MICSUM_GEN_URI));
  584.     var generator = this.getGenerator(generatorURI);
  585.  
  586.     return new Microsummary(pageURI, generator);
  587.   },
  588.  
  589.   /**
  590.    * Get a microsummary for a given page URI and generator URI.
  591.    *
  592.    * @param   pageURI
  593.    *          the URI of the page to be summarized
  594.    *
  595.    * @param   generatorURI
  596.    *          the URI of the microsummary generator
  597.    *
  598.    * @returns an nsIMicrosummary for the given page and generator URIs.
  599.    *
  600.    */
  601.   createMicrosummary: function MSS_createMicrosummary(pageURI, generatorURI) {
  602.     var generator = this.getGenerator(generatorURI);
  603.     return new Microsummary(pageURI, generator);
  604.   },
  605.  
  606.   /**
  607.    * Set the current microsummary for the given bookmark.
  608.    *
  609.    * @param   bookmarkID
  610.    *          the bookmark for which to set the current microsummary
  611.    *
  612.    * @param   microsummary
  613.    *          the microsummary to set as the current one
  614.    *
  615.    */
  616.   setMicrosummary: function MSS_setMicrosummary(bookmarkID, microsummary) {
  617.     this._setAnnotation(bookmarkID, ANNO_MICSUM_GEN_URI, microsummary.generator.uri.spec);
  618.  
  619.     if (microsummary.content)
  620.       this._updateMicrosummary(bookmarkID, microsummary);
  621.     else
  622.       this.refreshMicrosummary(bookmarkID);
  623.   },
  624.  
  625.   /**
  626.    * Remove the current microsummary for the given bookmark.
  627.    *
  628.    * @param   bookmarkID
  629.    *          the bookmark for which to remove the current microsummary
  630.    *
  631.    */
  632.   removeMicrosummary: function MSS_removeMicrosummary(bookmarkID) {
  633.     // Restore the user's title
  634.     if (this._ans.itemHasAnnotation(bookmarkID, ANNO_STATIC_TITLE))
  635.       this._bms.setItemTitle(bookmarkID, this._ans.getItemAnnotation(bookmarkID, ANNO_STATIC_TITLE));
  636.  
  637.     var fields = [ANNO_MICSUM_GEN_URI,
  638.                   ANNO_MICSUM_EXPIRATION,
  639.                   ANNO_STATIC_TITLE,
  640.                   ANNO_CONTENT_TYPE];
  641.  
  642.     for (let i = 0; i < fields.length; i++) {
  643.       var field = fields[i];
  644.       if (this._ans.itemHasAnnotation(bookmarkID, field))
  645.         this._ans.removeItemAnnotation(bookmarkID, field);
  646.     }
  647.   },
  648.  
  649.   /**
  650.    * Whether or not the given bookmark has a current microsummary.
  651.    *
  652.    * @param   bookmarkID
  653.    *          the bookmark for which to set the current microsummary
  654.    *
  655.    * @returns a boolean representing whether or not the given bookmark
  656.    *          has a current microsummary
  657.    *
  658.    */
  659.   hasMicrosummary: function MSS_hasMicrosummary(bookmarkID) {
  660.     return this._ans.itemHasAnnotation(bookmarkID, ANNO_MICSUM_GEN_URI);
  661.   },
  662.  
  663.   /**
  664.    * Whether or not the given microsummary is the current microsummary
  665.    * for the given bookmark.
  666.    *
  667.    * @param   bookmarkID
  668.    *          the bookmark to check
  669.    *
  670.    * @param   microsummary
  671.    *          the microsummary to check
  672.    *
  673.    * @returns whether or not the microsummary is the current one
  674.    *          for the bookmark
  675.    *
  676.    */
  677.   isMicrosummary: function MSS_isMicrosummary(aBookmarkID, aMicrosummary) {
  678.     if (!aMicrosummary || !aBookmarkID)
  679.       throw Cr.NS_ERROR_INVALID_ARG;
  680.  
  681.     if (this.hasMicrosummary(aBookmarkID)) {
  682.       currentMicrosummarry = this.getMicrosummary(aBookmarkID);
  683.       if (aMicrosummary.equals(currentMicrosummarry))
  684.         return true;
  685.     }
  686.     return false
  687.   },
  688.  
  689.   /**
  690.    * Refresh a microsummary, updating its value in the datastore and UI.
  691.    * If this method can refresh the microsummary instantly, it will.
  692.    * Otherwise, it'll asynchronously download the necessary information
  693.    * (the generator and/or page) before refreshing the microsummary.
  694.    *
  695.    * Callers should check the "content" property of the returned microsummary
  696.    * object to distinguish between sync and async refreshes.  If its value
  697.    * is "null", then it's an async refresh, and the caller should register
  698.    * itself as an nsIMicrosummaryObserver via nsIMicrosummary.addObserver()
  699.    * to find out when the refresh completes.
  700.    *
  701.    * @param   bookmarkID
  702.    *          the bookmark whose microsummary is being refreshed
  703.    *
  704.    * @returns the microsummary being refreshed
  705.    *
  706.    */
  707.   refreshMicrosummary: function MSS_refreshMicrosummary(bookmarkID) {
  708.     if (!this.hasMicrosummary(bookmarkID))
  709.       throw "bookmark " + bookmarkID + " does not have a microsummary";
  710.  
  711.     var pageURI = this._bms.getBookmarkURI(bookmarkID);
  712.     if (!pageURI)
  713.       throw("can't get URL for bookmark with ID " + bookmarkID);
  714.     var generatorURI = this._uri(this._ans.getItemAnnotation(bookmarkID,
  715.                                                              ANNO_MICSUM_GEN_URI));
  716.  
  717.     var generator = this._localGenerators[generatorURI.spec] ||
  718.                     new MicrosummaryGenerator(generatorURI);
  719.  
  720.     var microsummary = new Microsummary(pageURI, generator);
  721.  
  722.     // A microsummary observer that calls the microsummary service
  723.     // to update the datastore when the microsummary finishes loading.
  724.     var observer = {
  725.       _svc: this,
  726.       _bookmarkID: bookmarkID,
  727.       onContentLoaded: function MSS_observer_onContentLoaded(microsummary) {
  728.         try {
  729.           this._svc._updateMicrosummary(this._bookmarkID, microsummary);
  730.         }
  731.         finally {
  732.           this._svc = null;
  733.           this._bookmarkID = null;
  734.           microsummary.removeObserver(this);
  735.         }
  736.       },
  737.  
  738.       onError: function MSS_observer_onError(microsummary) {
  739.         if (microsummary.needsRemoval)
  740.           this._svc.removeMicrosummary(this._bookmarkID);
  741.       }
  742.     };
  743.  
  744.     // Register the observer with the microsummary and trigger the microsummary
  745.     // to update itself.
  746.     microsummary.addObserver(observer);
  747.     microsummary.update();
  748.     
  749.     return microsummary;
  750.   }
  751. };
  752.  
  753.  
  754.  
  755.  
  756.  
  757. function LiveTitleNotificationSubject(bookmarkID, microsummary) {
  758.   this.bookmarkID = bookmarkID;
  759.   this.microsummary = microsummary;
  760. }
  761.  
  762. LiveTitleNotificationSubject.prototype = {
  763.   bookmarkID: null,
  764.   microsummary: null,
  765.  
  766.   // nsISupports
  767.   QueryInterface: XPCOMUtils.generateQI([Ci.nsILiveTitleNotificationSubject]),
  768. };
  769.  
  770.  
  771.  
  772.  
  773.  
  774. function Microsummary(aPageURI, aGenerator) {
  775.   this._observers = [];
  776.   this._pageURI = aPageURI || null;
  777.   this._generator = aGenerator || null;
  778.   this._content = null;
  779.   this._pageContent = null;
  780.   this._updateInterval = null;
  781.   this._needsRemoval = false;
  782. }
  783.  
  784. Microsummary.prototype = {
  785.   // The microsummary service.
  786.   __mss: null,
  787.   get _mss() {
  788.     if (!this.__mss)
  789.       this.__mss = Cc["@mozilla.org/microsummary/service;1"].
  790.                    getService(Ci.nsIMicrosummaryService);
  791.     return this.__mss;
  792.   },
  793.  
  794.   // IO Service
  795.   __ios: null,
  796.   get _ios() {
  797.     if (!this.__ios)
  798.       this.__ios = Cc["@mozilla.org/network/io-service;1"].
  799.                    getService(Ci.nsIIOService);
  800.     return this.__ios;
  801.   },
  802.  
  803.   /**
  804.    * Make a URI from a spec.
  805.    * @param   spec
  806.    *          The string spec of the URI.
  807.    * @returns An nsIURI object.
  808.    */
  809.   _uri: function MSS__uri(spec) {
  810.     return this._ios.newURI(spec, null, null);
  811.   },
  812.  
  813.   // nsISupports
  814.   QueryInterface: XPCOMUtils.generateQI([Ci.nsIMicrosummary]),
  815.  
  816.   // nsIMicrosummary
  817.   get content() {
  818.     // If we have everything we need to generate the content, generate it.
  819.     if (!this._content &&
  820.         this.generator.loaded &&
  821.         (this.pageContent || !this.generator.needsPageContent)) {
  822.       this._content = this.generator.generateMicrosummary(this.pageContent);
  823.       this._updateInterval = this.generator.calculateUpdateInterval(this.pageContent);
  824.     }
  825.  
  826.     // Note: we return "null" if the content wasn't already generated and we
  827.     // couldn't retrieve it from the generated title annotation or generate it
  828.     // ourselves.  So callers shouldn't count on getting content; instead,
  829.     // they should call update if the return value of this getter is "null",
  830.     // setting an observer to tell them when content generation is done.
  831.     return this._content;
  832.   },
  833.  
  834.   get generator()            { return this._generator },
  835.   set generator(newValue)    { return this._generator = newValue },
  836.  
  837.   get pageURI() { return this._pageURI },
  838.  
  839.   equals: function(aOther) {
  840.     if (this._generator &&
  841.         this._pageURI.equals(aOther.pageURI) &&
  842.         this._generator.equals(aOther.generator))
  843.       return true;
  844.  
  845.     return false;
  846.   },
  847.  
  848.   get pageContent() {
  849.     if (!this._pageContent) {
  850.       // If the page is currently loaded into a browser window, use that.
  851.       var resource = getLoadedMicrosummaryResource(this.pageURI);
  852.       if (resource) {
  853.         this._pageContent = resource.content;
  854.         resource.destroy();
  855.       }
  856.     }
  857.  
  858.     return this._pageContent;
  859.   },
  860.   set pageContent(newValue) { return this._pageContent = newValue },
  861.  
  862.   get updateInterval()         { return this._updateInterval; },
  863.   set updateInterval(newValue) { return this._updateInterval = newValue; },
  864.  
  865.   get needsRemoval() { return this._needsRemoval; },
  866.  
  867.   // nsIMicrosummary
  868.  
  869.   addObserver: function MS_addObserver(observer) {
  870.     // Register the observer, but only if it isn't already registered,
  871.     // so that we don't call the same observer twice for any given change.
  872.     if (this._observers.indexOf(observer) == -1)
  873.       this._observers.push(observer);
  874.   },
  875.   
  876.   removeObserver: function MS_removeObserver(observer) {
  877.     //NS_ASSERT(this._observers.indexOf(observer) != -1,
  878.     //          "can't remove microsummary observer " + observer + ": not registered");
  879.   
  880.     //this._observers =
  881.     //  this._observers.filter(function(i) { observer != i });
  882.     if (this._observers.indexOf(observer) != -1)
  883.       this._observers.splice(this._observers.indexOf(observer), 1);
  884.   },
  885.  
  886.   /**
  887.    * Regenerates the microsummary, asynchronously downloading its generator
  888.    * and content as needed.
  889.    *
  890.    */
  891.   update: function MS_update() {
  892.     LOG("microsummary.update called for page:\n  " + this.pageURI.spec +
  893.         "\nwith generator:\n  " + this.generator.uri.spec);
  894.  
  895.     var t = this;
  896.  
  897.     // We use a common error callback here to flag this microsummary for removal
  898.     // if either the generator or page content have gone permanently missing.
  899.     var errorCallback = function MS_errorCallback(resource) {
  900.       if (resource.status == 410) {
  901.         t._needsRemoval = true;
  902.         LOG("server indicated " + resource.uri.spec + " is gone. flagging for removal");
  903.       }
  904.  
  905.       resource.destroy();
  906.  
  907.       for (let i = 0; i < t._observers.length; i++)
  908.         t._observers[i].onError(t);
  909.     };
  910.  
  911.     // If we don't have the generator, download it now.  After it downloads,
  912.     // we'll re-call this method to continue updating the microsummary.
  913.     if (!this.generator.loaded) {
  914.       // If this generator is identified by a URN, then it's a local generator
  915.       // that should have been cached on application start, so it's missing.
  916.       if (this.generator.uri.scheme == "urn") {
  917.         // If it was installed via nsSidebar::addMicrosummaryGenerator (i.e. it
  918.         // has a URN that identifies the source URL from which we installed it),
  919.         // try to reinstall it (once).
  920.         if (/^source:/.test(this.generator.uri.path)) {
  921.           this._reinstallMissingGenerator();
  922.           return;
  923.         }
  924.         else
  925.           throw "missing local generator: " + this.generator.uri.spec;
  926.       }
  927.  
  928.       LOG("generator not yet loaded; downloading it");
  929.       var generatorCallback =
  930.         function MS_generatorCallback(resource) {
  931.           try     { t._handleGeneratorLoad(resource) }
  932.           finally { resource.destroy() }
  933.         };
  934.       var resource = new MicrosummaryResource(this.generator.uri);
  935.       resource.load(generatorCallback, errorCallback);
  936.       return;
  937.     }
  938.  
  939.     // If we need the page content, and we don't have it, download it now.
  940.     // Afterwards we'll re-call this method to continue updating the microsummary.
  941.     if (this.generator.needsPageContent && !this.pageContent) {
  942.       LOG("page content not yet loaded; downloading it");
  943.       var pageCallback =
  944.         function MS_pageCallback(resource) {
  945.           try     { t._handlePageLoad(resource) }
  946.           finally { resource.destroy() }
  947.         };
  948.       var resource = new MicrosummaryResource(this.pageURI);
  949.       resource.load(pageCallback, errorCallback);
  950.       return;
  951.     }
  952.  
  953.     LOG("generator (and page, if needed) both loaded; generating microsummary");
  954.  
  955.     // Now that we have both the generator and (if needed) the page content,
  956.     // generate the microsummary, then let the observers know about it.
  957.     this._content = this.generator.generateMicrosummary(this.pageContent);
  958.     this._updateInterval = this.generator.calculateUpdateInterval(this.pageContent);
  959.     this.pageContent = null;
  960.     for ( var i = 0; i < this._observers.length; i++ )
  961.       this._observers[i].onContentLoaded(this);
  962.  
  963.     LOG("generated microsummary: " + this.content);
  964.   },
  965.  
  966.   _handleGeneratorLoad: function MS__handleGeneratorLoad(resource) {
  967.     LOG(this.generator.uri.spec + " microsummary generator downloaded");
  968.     if (resource.isXML)
  969.       this.generator.initFromXML(resource.content);
  970.     else if (resource.contentType == "text/plain")
  971.       this.generator.initFromText(resource.content);
  972.     else if (resource.contentType == "text/html")
  973.       this.generator.initFromText(resource.content.body.textContent);
  974.     else
  975.       throw("generator is neither XML nor plain text");
  976.  
  977.     // Only trigger a [content] update if we were able to init the generator. 
  978.     if (this.generator.loaded)
  979.       this.update();
  980.   },
  981.  
  982.   _handlePageLoad: function MS__handlePageLoad(resource) {
  983.     if (!resource.isXML && resource.contentType != "text/html")
  984.       throw("page is neither HTML nor XML");
  985.  
  986.     this.pageContent = resource.content;
  987.     this.update();
  988.   },
  989.  
  990.   /**
  991.    * Try to reinstall a missing local generator that was originally installed
  992.    * from a URL using nsSidebar::addMicrosumaryGenerator.
  993.    *
  994.    */
  995.   _reinstallMissingGenerator: function MS__reinstallMissingGenerator() {
  996.     LOG("attempting to reinstall missing generator " + this.generator.uri.spec);
  997.  
  998.     var t = this;
  999.  
  1000.     var loadCallback =
  1001.       function MS_missingGeneratorLoadCallback(resource) {
  1002.         try     { t._handleMissingGeneratorLoad(resource) }
  1003.         finally { resource.destroy() }
  1004.       };
  1005.  
  1006.     var errorCallback =
  1007.       function MS_missingGeneratorErrorCallback(resource) {
  1008.         try     { t._handleMissingGeneratorError(resource) }
  1009.         finally { resource.destroy() }
  1010.       };
  1011.  
  1012.     try {
  1013.       // Extract the URI from which the generator was originally installed.
  1014.       var sourceURL = this.generator.uri.path.replace(/^source:/, "");
  1015.       var sourceURI = this._uri(sourceURL);
  1016.  
  1017.       var resource = new MicrosummaryResource(sourceURI);
  1018.       resource.load(loadCallback, errorCallback);
  1019.     }
  1020.     catch(ex) {
  1021.       Cu.reportError(ex);
  1022.       this._handleMissingGeneratorError();
  1023.     }
  1024.   },
  1025.  
  1026.   /**
  1027.    * Handle a load event for a missing local generator by trying to reinstall
  1028.    * the generator.  If this fails, call _handleMissingGeneratorError to unset
  1029.    * microsummaries for bookmarks using this generator so we don't repeatedly
  1030.    * try to reinstall the generator, creating too much traffic to the website
  1031.    * from which we downloaded it.
  1032.    *
  1033.    * @param resource
  1034.    *        the nsIMicrosummaryResource representing the downloaded generator
  1035.    *
  1036.    */
  1037.   _handleMissingGeneratorLoad: function MS__handleMissingGeneratorLoad(resource) {
  1038.     try {
  1039.       // Make sure the generator is XML, since local generators have to be.
  1040.       if (!resource.isXML)
  1041.         throw("downloaded, but not XML " + this.generator.uri.spec);
  1042.  
  1043.       // Store the generator's ID in its XML definition.
  1044.       var generatorID = this.generator.uri.spec;
  1045.       resource.content.documentElement.setAttribute("uri", generatorID);
  1046.  
  1047.       // Reinstall the generator and replace our placeholder generator object
  1048.       // with the newly installed generator.
  1049.       this.generator = this._mss.installGenerator(resource.content);
  1050.  
  1051.       // A reinstalled generator should always be loaded.  But just in case
  1052.       // it isn't, throw an error so we don't get into an infinite loop
  1053.       // (otherwise this._update would try again to reinstall it).
  1054.       if (!this.generator.loaded)
  1055.         throw("supposedly installed, but not in cache " + this.generator.uri.spec);
  1056.     }
  1057.     catch(ex) {
  1058.       Cu.reportError(ex);
  1059.       this._handleMissingGeneratorError(resource);
  1060.       return;
  1061.     }
  1062.   
  1063.     LOG("reinstall succeeded; resuming update " + this.generator.uri.spec);
  1064.     this.update();
  1065.   },
  1066.  
  1067.   /**
  1068.    * Handle an error event for a missing local generator load by unsetting
  1069.    * the microsummaries for bookmarks using this generator so we don't
  1070.    * repeatedly try to reinstall the generator, creating too much traffic
  1071.    * to the website from which we downloaded it.
  1072.    *
  1073.    * @param resource
  1074.    *        the nsIMicrosummaryResource representing the downloaded generator
  1075.    *
  1076.    */
  1077.   _handleMissingGeneratorError: function MS__handleMissingGeneratorError(resource) {
  1078.     LOG("reinstall failed; removing microsummaries " + this.generator.uri.spec);
  1079.     var bookmarks = this._mss.getBookmarks();
  1080.     while (bookmarks.hasMoreElements()) {
  1081.       var bookmarkID = bookmarks.getNext();
  1082.       var microsummary = this._mss.getMicrosummary(bookmarkID);
  1083.       if (microsummary.generator.uri.equals(this.generator.uri)) {
  1084.         LOG("removing microsummary for " + microsummary.pageURI.spec);
  1085.         this._mss.removeMicrosummary(bookmarkID);
  1086.       }
  1087.     }
  1088.   }
  1089.  
  1090. };
  1091.  
  1092.  
  1093.  
  1094.  
  1095.  
  1096. function MicrosummaryGenerator(aURI, aLocalURI, aName) {
  1097.   this._uri = aURI || null;
  1098.   this._localURI = aLocalURI || null;
  1099.   this._name = aName || null;
  1100.   this._loaded = false;
  1101.   this._rules = [];
  1102.   this._template = null;
  1103.   this._content = null;
  1104. }
  1105.  
  1106. MicrosummaryGenerator.prototype = {
  1107.  
  1108.   // IO Service
  1109.   __ios: null,
  1110.   get _ios() {
  1111.     if (!this.__ios)
  1112.       this.__ios = Cc["@mozilla.org/network/io-service;1"].
  1113.                    getService(Ci.nsIIOService);
  1114.     return this.__ios;
  1115.   },
  1116.  
  1117.   // nsISupports
  1118.   QueryInterface: XPCOMUtils.generateQI([Ci.nsIMicrosummaryGenerator]),
  1119.  
  1120.   // nsIMicrosummaryGenerator
  1121.  
  1122.   // Normally this is just the URL from which we download the generator,
  1123.   // but for generators stored in the app or profile generators directory
  1124.   // it's the value of the generator tag's "uri" attribute (or its local URI
  1125.   // should the "uri" attribute be missing).
  1126.   get uri() { return this._uri || this.localURI },
  1127.  
  1128.   // For generators bundled with the browser or installed by the user,
  1129.   // the local URI is the URI of the local file containing the generator XML.
  1130.   get localURI() { return this._localURI },
  1131.   get name() { return this._name },
  1132.   get loaded() { return this._loaded },
  1133.  
  1134.   equals: function(aOther) {
  1135.     // XXX: could the uri attribute for an exposed generator ever be null?
  1136.     return aOther.uri.equals(this.uri);
  1137.   },
  1138.  
  1139.   /**
  1140.    * Determines whether or not the generator applies to a given URI.
  1141.    * By default, the generator does not apply to any URI.  In order for it
  1142.    * to apply to a URI, the URI must match one or more of the generator's
  1143.    * "include" rules and not match any of the generator's "exclude" rules.
  1144.    *
  1145.    * @param   uri
  1146.    *          the URI to test to see if this generator applies to it
  1147.    *
  1148.    * @returns boolean
  1149.    *          whether or not the generator applies to the given URI
  1150.    *
  1151.    */
  1152.   appliesToURI: function(uri) {
  1153.     var applies = false;
  1154.  
  1155.     for ( var i = 0 ; i < this._rules.length ; i++ ) {
  1156.       var rule = this._rules[i];
  1157.  
  1158.       switch (rule.type) {
  1159.       case "include":
  1160.         if (rule.regexp.test(uri.spec))
  1161.           applies = true;
  1162.         break;
  1163.       case "exclude":
  1164.         if (rule.regexp.test(uri.spec))
  1165.           return false;
  1166.         break;
  1167.       }
  1168.     }
  1169.  
  1170.     return applies;
  1171.   },
  1172.  
  1173.   get needsPageContent() {
  1174.     if (this._template)
  1175.       return true;
  1176.     if (this._content)
  1177.       return false;
  1178.  
  1179.     throw("needsPageContent called on uninitialized microsummary generator");
  1180.   },
  1181.  
  1182.   /**
  1183.    * Initializes a generator from text content.  Generators initialized
  1184.    * from text content merely return that content when their generate() method
  1185.    * gets called.
  1186.    *
  1187.    * @param   text
  1188.    *          the text content
  1189.    */
  1190.   initFromText: function(text) {
  1191.     this._content = text;
  1192.     this._loaded = true;
  1193.   },
  1194.  
  1195.   /**
  1196.    * Initializes a generator from an XML description of it.
  1197.    * 
  1198.    * @param   xmlDocument
  1199.    *          An XMLDocument object describing a microsummary generator.
  1200.    *
  1201.    */
  1202.   initFromXML: function(xmlDocument) {
  1203.     // XXX Make sure the argument is a valid generator XML document.
  1204.  
  1205.     // XXX I would have wanted to retrieve the info from the XML via E4X,
  1206.     // but we'll need to pass the XSLT transform sheet to the XSLT processor,
  1207.     // and the processor can't deal with an E4X-wrapped template node.
  1208.  
  1209.     // XXX Right now the code retrieves the first "generator" element
  1210.     // in the microsummaries namespace, regardless of whether or not
  1211.     // it's the root element.  Should it matter?
  1212.     var generatorNode = xmlDocument.getElementsByTagNameNS(MICSUM_NS, "generator")[0];
  1213.     if (!generatorNode)
  1214.       throw Cr.NS_ERROR_FAILURE;
  1215.  
  1216.     this._name = generatorNode.getAttribute("name");
  1217.  
  1218.     // We have to retrieve the URI from local generators via the "uri" attribute
  1219.     // of its generator tag.
  1220.     if (this.localURI && generatorNode.hasAttribute("uri"))
  1221.       this._uri = this._ios.newURI(generatorNode.getAttribute("uri"), null, null);
  1222.  
  1223.     function getFirstChildByTagName(tagName, parentNode, namespace) {
  1224.       var nodeList = parentNode.getElementsByTagNameNS(namespace, tagName);
  1225.       for (var i = 0; i < nodeList.length; i++) {
  1226.         // Make sure that the node is a direct descendent of the generator node
  1227.         if (nodeList[i].parentNode == parentNode)
  1228.           return nodeList[i];
  1229.       }
  1230.       return null;
  1231.     }
  1232.  
  1233.     // Slurp the include/exclude rules that determine the pages to which
  1234.     // this generator applies.  Order is important, so we add the rules
  1235.     // in the order in which they appear in the XML.
  1236.     this._rules.splice(0);
  1237.     var pages = getFirstChildByTagName("pages", generatorNode, MICSUM_NS);
  1238.     if (pages) {
  1239.       // XXX Make sure the pages tag exists.
  1240.       for ( var i = 0; i < pages.childNodes.length ; i++ ) {
  1241.         var node = pages.childNodes[i];
  1242.         if (node.nodeType != node.ELEMENT_NODE ||
  1243.             node.namespaceURI != MICSUM_NS ||
  1244.             (node.nodeName != "include" && node.nodeName != "exclude"))
  1245.           continue;
  1246.         var urlRegexp = node.textContent.replace(/^\s+|\s+$/g, "");
  1247.         this._rules.push({ type: node.nodeName, regexp: new RegExp(urlRegexp) });
  1248.       }
  1249.     }
  1250.  
  1251.     // allow the generators to set individual update values (even varying
  1252.     // depending on certain XPath expressions)
  1253.     var update = getFirstChildByTagName("update", generatorNode, MICSUM_NS);
  1254.     if (update) {
  1255.       function _parseInterval(string) {
  1256.         // convert from minute fractions to milliseconds
  1257.         // and ensure a minimum value of 1 minute
  1258.         return Math.round(Math.max(parseFloat(string) || 0, 1) * 60 * 1000);
  1259.       }
  1260.  
  1261.       this._unconditionalUpdateInterval =
  1262.         update.hasAttribute("interval") ?
  1263.         _parseInterval(update.getAttribute("interval")) : null;
  1264.  
  1265.       // collect the <condition expression="XPath Expression" interval="time"/> clauses
  1266.       this._updateIntervals = new Array();
  1267.       for (i = 0; i < update.childNodes.length; i++) {
  1268.         node = update.childNodes[i];
  1269.         if (node.nodeType != node.ELEMENT_NODE || node.namespaceURI != MICSUM_NS ||
  1270.             node.nodeName != "condition")
  1271.           continue;
  1272.         if (!node.getAttribute("expression") || !node.getAttribute("interval")) {
  1273.           LOG("ignoring incomplete conditional update interval for " + this.uri.spec);
  1274.           continue;
  1275.         }
  1276.         this._updateIntervals.push({
  1277.           expression: node.getAttribute("expression"),
  1278.           interval: _parseInterval(node.getAttribute("interval"))
  1279.         });
  1280.       }
  1281.     }
  1282.  
  1283.     var templateNode = getFirstChildByTagName("template", generatorNode, MICSUM_NS);
  1284.     if (templateNode) {
  1285.       this._template = getFirstChildByTagName("transform", templateNode, XSLT_NS) ||
  1286.                        getFirstChildByTagName("stylesheet", templateNode, XSLT_NS);
  1287.     }
  1288.     // XXX Make sure the template is a valid XSL transform sheet.
  1289.  
  1290.     this._loaded = true;
  1291.   },
  1292.  
  1293.   generateMicrosummary: function MSD_generateMicrosummary(pageContent) {
  1294.  
  1295.     var content;
  1296.  
  1297.     if (this._content)
  1298.       content = this._content;
  1299.     else if (this._template)
  1300.       content = this._processTemplate(pageContent);
  1301.     else
  1302.       throw("generateMicrosummary called on uninitialized microsummary generator");
  1303.  
  1304.     // Clean up the output
  1305.     content = content.replace(/^\s+|\s+$/g, "");
  1306.     if (content.length > MAX_SUMMARY_LENGTH) 
  1307.       content = content.substring(0, MAX_SUMMARY_LENGTH);
  1308.  
  1309.     return content;
  1310.   },
  1311.  
  1312.   calculateUpdateInterval: function MSD_calculateUpdateInterval(doc) {
  1313.     if (this._content || !this._updateIntervals || !doc)
  1314.       return null;
  1315.  
  1316.     for (var i = 0; i < this._updateIntervals.length; i++) {
  1317.       try {
  1318.         if (doc.evaluate(this._updateIntervals[i].expression, doc, null,
  1319.                          Ci.nsIDOMXPathResult.BOOLEAN_TYPE, null).booleanValue)
  1320.           return this._updateIntervals[i].interval;
  1321.       }
  1322.       catch (ex) {
  1323.         Cu.reportError(ex);
  1324.         // remove the offending conditional update interval
  1325.         this._updateIntervals.splice(i--, 1);
  1326.       }
  1327.     }
  1328.  
  1329.     return this._unconditionalUpdateInterval;
  1330.   },
  1331.  
  1332.   _processTemplate: function MSD__processTemplate(doc) {
  1333.     LOG("processing template " + this._template + " against document " + doc);
  1334.  
  1335.     // XXX Should we just have one global instance of the processor?
  1336.     var processor = Cc["@mozilla.org/document-transformer;1?type=xslt"].
  1337.                     createInstance(Ci.nsIXSLTProcessor);
  1338.  
  1339.     // Turn off document loading of all kinds (document(), <include>, <import>)
  1340.     // for security (otherwise local generators would be able to load local files).
  1341.     processor.flags |= Ci.nsIXSLTProcessorPrivate.DISABLE_ALL_LOADS;
  1342.  
  1343.     processor.importStylesheet(this._template);
  1344.     var fragment = processor.transformToFragment(doc, doc);
  1345.  
  1346.     LOG("template processing result: " + fragment.textContent);
  1347.  
  1348.     // XXX When we support HTML microsummaries we'll need to do something
  1349.     // more sophisticated than just returning the text content of the fragment.
  1350.     return fragment.textContent;
  1351.   },
  1352.  
  1353.   saveXMLToFile: function MSD_saveXMLToFile(xmlDefinition) {
  1354.     var file = this.localURI.QueryInterface(Ci.nsIFileURL).file.clone();
  1355.  
  1356.     LOG("saving definition to " + file.path);
  1357.  
  1358.     // Write the generator XML to the local file.
  1359.     var outputStream = Cc["@mozilla.org/network/safe-file-output-stream;1"].
  1360.                        createInstance(Ci.nsIFileOutputStream);
  1361.     var localFile = file.QueryInterface(Ci.nsILocalFile);
  1362.     outputStream.init(localFile, (MODE_WRONLY | MODE_TRUNCATE | MODE_CREATE),
  1363.                       PERMS_FILE, 0);
  1364.     var serializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"].
  1365.                      createInstance(Ci.nsIDOMSerializer);
  1366.     serializer.serializeToStream(xmlDefinition, outputStream, null);
  1367.     if (outputStream instanceof Ci.nsISafeOutputStream) {
  1368.       try       { outputStream.finish() }
  1369.       catch (e) { outputStream.close()  }
  1370.     }
  1371.     else
  1372.       outputStream.close();
  1373.   },
  1374.  
  1375.   update: function MSD_update() {
  1376.     // Update this generator if it was downloaded from a remote source and has
  1377.     // been modified since we last downloaded it.
  1378.     var genURI = this.uri;
  1379.     if (genURI && /^urn:source:/i.test(genURI.spec)) {
  1380.       let genURL = genURI.spec.replace(/^urn:source:/, "");
  1381.       genURI = this._ios.newURI(genURL, null, null);
  1382.     }
  1383.  
  1384.     // Only continue if we have a valid remote URI
  1385.     if (!genURI || !/^https?/.test(genURI.scheme)) {
  1386.       LOG("generator did not have valid URI; skipping update: " + genURI.spec);
  1387.       return;
  1388.     }
  1389.  
  1390.     // We use a HEAD request to check if the generator has been modified since
  1391.     // the last time we downloaded it. If it has, we move to _preformUpdate() to
  1392.     // actually download and save the new generator.
  1393.     var t = this;
  1394.     var loadCallback = function(resource) {
  1395.       if (resource.status != 304)
  1396.         t._performUpdate(genURI);
  1397.       else
  1398.         LOG("generator is already up to date: " + genURI.spec);
  1399.       resource.destroy();
  1400.     };
  1401.     var errorCallback = function(resource) {
  1402.       resource.destroy();
  1403.     };
  1404.  
  1405.     var file = this.localURI.QueryInterface(Ci.nsIFileURL).file.clone();
  1406.     var lastmod = new Date(file.lastModifiedTime);
  1407.     LOG("updating generator: " + genURI.spec);
  1408.     var resource = new MicrosummaryResource(genURI);
  1409.     resource.lastMod = lastmod.toUTCString();
  1410.     resource.method = "HEAD";
  1411.     resource.load(loadCallback, errorCallback);
  1412.   },
  1413.  
  1414.   _performUpdate: function MSD__performUpdate(uri) {
  1415.     var t = this;
  1416.     var loadCallback = function(resource) {
  1417.       try     { t._handleUpdateLoad(resource) }
  1418.       finally { resource.destroy() }
  1419.     };
  1420.     var errorCallback = function(resource) {
  1421.       resource.destroy();
  1422.     };
  1423.  
  1424.     var resource = new MicrosummaryResource(uri);
  1425.     resource.load(loadCallback, errorCallback);
  1426.   },
  1427.  
  1428.   _handleUpdateLoad: function MSD__handleUpdateLoad(resource) {
  1429.     if (!resource.isXML)
  1430.       throw("update failed, downloaded resource is not XML: " + this.uri.spec);
  1431.  
  1432.     // Preserve the generator's ID.
  1433.     // XXX Check for redirects and update the URI if it changes.
  1434.     var generatorID = this.uri.spec;
  1435.     resource.content.documentElement.setAttribute("uri", generatorID);
  1436.  
  1437.     // Reinitialize this generator with the newly downloaded XML and save to disk.
  1438.     this.initFromXML(resource.content);
  1439.     this.saveXMLToFile(resource.content);
  1440.  
  1441.     // Let observers know we've updated this generator
  1442.     var obs = Cc["@mozilla.org/observer-service;1"].
  1443.               getService(Ci.nsIObserverService);
  1444.     obs.notifyObservers(this, "microsummary-generator-updated", null);
  1445.   }
  1446. };
  1447.  
  1448.  
  1449.  
  1450.  
  1451.  
  1452. // Microsummary sets are collections of microsummaries.  They allow callers
  1453. // to register themselves as observers of the set, and when any microsummary
  1454. // in the set changes, the observers get notified.  Thus a caller can observe
  1455. // the set instead of each individual microsummary.
  1456.  
  1457. function MicrosummarySet() {
  1458.   this._observers = [];
  1459.   this._elements = [];
  1460. }
  1461.  
  1462. MicrosummarySet.prototype = {
  1463.   // IO Service
  1464.   __ios: null,
  1465.   get _ios() {
  1466.     if (!this.__ios)
  1467.       this.__ios = Cc["@mozilla.org/network/io-service;1"].
  1468.                    getService(Ci.nsIIOService);
  1469.     return this.__ios;
  1470.   },
  1471.  
  1472.   QueryInterface: XPCOMUtils.generateQI([Ci.nsIMicrosummarySet,
  1473.                                          Ci.nsIMicrosummaryObserver]),
  1474.  
  1475.   // nsIMicrosummaryObserver
  1476.  
  1477.   onContentLoaded: function MSSet_onContentLoaded(microsummary) {
  1478.     for ( var i = 0; i < this._observers.length; i++ )
  1479.       this._observers[i].onContentLoaded(microsummary);
  1480.   },
  1481.  
  1482.   onError: function MSSet_onError(microsummary) {
  1483.     for ( var i = 0; i < this._observers.length; i++ )
  1484.       this._observers[i].onError(microsummary);
  1485.   },
  1486.  
  1487.   // nsIMicrosummarySet
  1488.  
  1489.   addObserver: function MSSet_addObserver(observer) {
  1490.     if (this._observers.length == 0) {
  1491.       for ( var i = 0 ; i < this._elements.length ; i++ )
  1492.         this._elements[i].addObserver(this);
  1493.     }
  1494.  
  1495.     // Register the observer, but only if it isn't already registered,
  1496.     // so that we don't call the same observer twice for any given change.
  1497.     if (this._observers.indexOf(observer) == -1)
  1498.       this._observers.push(observer);
  1499.   },
  1500.   
  1501.   removeObserver: function MSSet_removeObserver(observer) {
  1502.     //NS_ASSERT(this._observers.indexOf(observer) != -1,
  1503.     //          "can't remove microsummary observer " + observer + ": not registered");
  1504.   
  1505.     //this._observers =
  1506.     //  this._observers.filter(function(i) { observer != i });
  1507.     if (this._observers.indexOf(observer) != -1)
  1508.       this._observers.splice(this._observers.indexOf(observer), 1);
  1509.     
  1510.     if (this._observers.length == 0) {
  1511.       for ( var i = 0 ; i < this._elements.length ; i++ )
  1512.         this._elements[i].removeObserver(this);
  1513.     }
  1514.   },
  1515.  
  1516.   extractFromPage: function MSSet_extractFromPage(resource) {
  1517.     if (!resource.isXML && resource.contentType != "text/html")
  1518.       throw("page is neither HTML nor XML");
  1519.  
  1520.     // XXX Handle XML documents, whose microsummaries are specified
  1521.     // via processing instructions.
  1522.  
  1523.     var links = resource.content.getElementsByTagName("link");
  1524.     for ( var i = 0; i < links.length; i++ ) {
  1525.       var link = links[i];
  1526.  
  1527.       if(!link.hasAttribute("rel"))
  1528.         continue;
  1529.  
  1530.       var relAttr = link.getAttribute("rel");
  1531.  
  1532.       // The attribute's value can be a space-separated list of link types,
  1533.       // check to see if "microsummary" is one of them.
  1534.       var linkTypes = relAttr.split(/\s+/);
  1535.       if (!linkTypes.some( function(v) { return v.toLowerCase() == "microsummary"; }))
  1536.         continue;
  1537.  
  1538.  
  1539.       // Look for a TITLE attribute to give the generator a nice name in the UI.
  1540.       var linkTitle = link.getAttribute("title");
  1541.  
  1542.  
  1543.       // Unlike the "href" attribute, the "href" property contains
  1544.       // an absolute URI spec, so we use it here to create the URI.
  1545.       var generatorURI = this._ios.newURI(link.href,
  1546.                                           resource.content.characterSet,
  1547.                                           null);
  1548.  
  1549.       if (!/^https?$/i.test(generatorURI.scheme)) {
  1550.         LOG("can't load generator " + generatorURI.spec + " from page " +
  1551.             resource.uri.spec);
  1552.         continue;
  1553.       }
  1554.  
  1555.       var generator = new MicrosummaryGenerator(generatorURI, null, linkTitle);
  1556.       var microsummary = new Microsummary(resource.uri, generator);
  1557.       if (!this.hasItemForMicrosummary(microsummary))
  1558.         this.AppendElement(microsummary);
  1559.     }
  1560.   },
  1561.  
  1562.   /**
  1563.    * Determines whether the given microsumary is already represented in the
  1564.    * set.
  1565.    */
  1566.   hasItemForMicrosummary: function MSSet_hasItemForMicrosummary(aMicrosummary) {
  1567.     for (var i = 0; i < this._elements.length; i++) {
  1568.       if (this._elements[i].equals(aMicrosummary))
  1569.         return true;
  1570.     }
  1571.     return false;
  1572.   },
  1573.  
  1574.   // XXX Turn this into a complete implementation of nsICollection?
  1575.   AppendElement: function MSSet_AppendElement(element) {
  1576.     // Query the element to a microsummary.
  1577.     // XXX Should we NS_ASSERT if this fails?
  1578.     element = element.QueryInterface(Ci.nsIMicrosummary);
  1579.  
  1580.     if (this._elements.indexOf(element) == -1) {
  1581.       this._elements.push(element);
  1582.       element.addObserver(this);
  1583.     }
  1584.  
  1585.     // Notify observers that an element has been appended.
  1586.     for ( var i = 0; i < this._observers.length; i++ )
  1587.       this._observers[i].onElementAppended(element);
  1588.   },
  1589.  
  1590.   Enumerate: function MSSet_Enumerate() {
  1591.     return new ArrayEnumerator(this._elements);
  1592.   }
  1593. };
  1594.  
  1595.  
  1596.  
  1597.  
  1598.  
  1599. /**
  1600.  * An enumeration of items in a JS array.
  1601.  * @constructor
  1602.  */
  1603. function ArrayEnumerator(aItems) {
  1604.   if (aItems) {
  1605.     for (var i = 0; i < aItems.length; ++i) {
  1606.       if (!aItems[i])
  1607.         aItems.splice(i--, 1);
  1608.     }
  1609.     this._contents = aItems;
  1610.   } else {
  1611.     this._contents = [];
  1612.   }
  1613. }
  1614.  
  1615. ArrayEnumerator.prototype = {
  1616.   QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]),
  1617.  
  1618.   _index: 0,
  1619.  
  1620.   hasMoreElements: function() {
  1621.     return this._index < this._contents.length;
  1622.   },
  1623.  
  1624.   getNext: function() {
  1625.     return this._contents[this._index++];      
  1626.   }
  1627. };
  1628.  
  1629.  
  1630.  
  1631.  
  1632.  
  1633. /**
  1634.  * Outputs aText to the JavaScript console as well as to stdout if the
  1635.  * microsummary logging pref (browser.microsummary.log) is set to true.
  1636.  * 
  1637.  * @param aText
  1638.  *        the text to log
  1639.  */
  1640. function LOG(aText) {
  1641.   var f = arguments.callee;
  1642.   if (!("_enabled" in f))
  1643.     f._enabled = getPref("browser.microsummary.log", false);
  1644.   if (f._enabled) {
  1645.     dump("*** Microsummaries: " +  aText + "\n");
  1646.     var consoleService = Cc["@mozilla.org/consoleservice;1"].
  1647.                          getService(Ci.nsIConsoleService);
  1648.     consoleService.logStringMessage(aText);
  1649.   }
  1650. }
  1651.  
  1652.  
  1653.  
  1654.  
  1655.  
  1656. /**
  1657.  * A resource (page, microsummary, generator, etc.) identifiable by URI.
  1658.  * This object abstracts away much of the code for loading resources
  1659.  * and parsing their content if they are XML or HTML.
  1660.  * 
  1661.  * @constructor
  1662.  * 
  1663.  * @param   uri
  1664.  *          the location of the resource
  1665.  *
  1666.  */
  1667. function MicrosummaryResource(uri) {
  1668.   // Make sure we're not loading javascript: or data: URLs, which could
  1669.   // take advantage of the load to run code with chrome: privileges.
  1670.   // XXX Perhaps use nsIScriptSecurityManager.checkLoadURI instead.
  1671.   if (!(uri.schemeIs("http") || uri.schemeIs("https") || uri.schemeIs("file")))
  1672.     throw NS_ERROR_DOM_BAD_URI;
  1673.  
  1674.   this._uri = uri;
  1675.   this._content = null;
  1676.   this._contentType = null;
  1677.   this._isXML = false;
  1678.   this.__authFailed = false;
  1679.   this._status = null;
  1680.   this._method = "GET";
  1681.   this._lastMod = null;
  1682.  
  1683.   // A function to call when we finish loading/parsing the resource.
  1684.   this._loadCallback = null;
  1685.   // A function to call if we get an error while loading/parsing the resource.
  1686.   this._errorCallback = null;
  1687.   // A hidden iframe to parse HTML content.
  1688.   this._iframe = null;
  1689. }
  1690.  
  1691. MicrosummaryResource.prototype = {
  1692.   // IO Service
  1693.   __ios: null,
  1694.   get _ios() {
  1695.     if (!this.__ios)
  1696.       this.__ios = Cc["@mozilla.org/network/io-service;1"].
  1697.                    getService(Ci.nsIIOService);
  1698.     return this.__ios;
  1699.   },
  1700.  
  1701.   get uri() {
  1702.     return this._uri;
  1703.   },
  1704.  
  1705.   get content() {
  1706.     return this._content;
  1707.   },
  1708.  
  1709.   get contentType() {
  1710.     return this._contentType;
  1711.   },
  1712.  
  1713.   get isXML() {
  1714.     return this._isXML;
  1715.   },
  1716.  
  1717.   get status()        { return this._status },
  1718.   set status(aStatus) { this._status = aStatus },
  1719.  
  1720.   get method()        { return this._method },
  1721.   set method(aMethod) { this._method = aMethod },
  1722.  
  1723.   get lastMod()     { return this._lastMod },
  1724.   set lastMod(aMod) { this._lastMod = aMod },
  1725.  
  1726.   // Implement notification callback interfaces so we can suppress UI
  1727.   // and abort loads for bad SSL certs and HTTP authorization requests.
  1728.   
  1729.   // Interfaces this component implements.
  1730.   interfaces: [Ci.nsIAuthPromptProvider,
  1731.                Ci.nsIAuthPrompt,
  1732.                Ci.nsIBadCertListener2,
  1733.                Ci.nsISSLErrorListener,
  1734.                Ci.nsIPrompt,
  1735.                Ci.nsIProgressEventSink,
  1736.                Ci.nsIInterfaceRequestor,
  1737.                Ci.nsISupports],
  1738.  
  1739.   // nsISupports
  1740.  
  1741.   QueryInterface: function MSR_QueryInterface(iid) {
  1742.     if (!this.interfaces.some( function(v) { return iid.equals(v) } ))
  1743.       throw Cr.NS_ERROR_NO_INTERFACE;
  1744.  
  1745.     // nsIAuthPrompt and nsIPrompt need separate implementations because
  1746.     // their method signatures conflict.  The other interfaces we implement
  1747.     // within MicrosummaryResource itself.
  1748.     switch(iid) {
  1749.     case Ci.nsIAuthPrompt:
  1750.       return this.authPrompt;
  1751.     case Ci.nsIPrompt:
  1752.       return this.prompt;
  1753.     default:
  1754.       return this;
  1755.     }
  1756.   },
  1757.  
  1758.   // nsIInterfaceRequestor
  1759.   
  1760.   getInterface: function MSR_getInterface(iid) {
  1761.     return this.QueryInterface(iid);
  1762.   },
  1763.  
  1764.   // nsIBadCertListener2
  1765.   // Suppress any certificate errors
  1766.   notifyCertProblem: function MSR_certProblem(socketInfo, status, targetSite) {
  1767.     return true;
  1768.   },
  1769.  
  1770.   // nsISSLErrorListener
  1771.   // Suppress any ssl errors
  1772.   notifySSLError: function MSR_SSLError(socketInfo, error, targetSite) {
  1773.     return true;
  1774.   },
  1775.  
  1776.   
  1777.   // Suppress UI and abort loads for files secured by authentication.
  1778.  
  1779.   // Auth requests appear to succeed when we cancel them (since the server
  1780.   // redirects us to a "you're not authorized" page), so we have to set a flag
  1781.   // to let the load handler know to treat the load as a failure.
  1782.   get _authFailed()         { return this.__authFailed; },
  1783.   set _authFailed(newValue) { return this.__authFailed = newValue },
  1784.  
  1785.   // nsIAuthPromptProvider
  1786.   
  1787.   getAuthPrompt: function(aPromptReason, aIID) {
  1788.     this._authFailed = true;
  1789.     throw Cr.NS_ERROR_NOT_AVAILABLE;
  1790.   },
  1791.  
  1792.   // HTTP always requests nsIAuthPromptProvider first, so it never needs
  1793.   // nsIAuthPrompt, but not all channels use nsIAuthPromptProvider, so we
  1794.   // implement nsIAuthPrompt too.
  1795.  
  1796.   // nsIAuthPrompt
  1797.  
  1798.   get authPrompt() {
  1799.     var resource = this;
  1800.     return {
  1801.       QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrompt]),
  1802.       prompt: function(dialogTitle, text, passwordRealm, savePassword, defaultText, result) {
  1803.         resource._authFailed = true;
  1804.         return false;
  1805.       },
  1806.       promptUsernameAndPassword: function(dialogTitle, text, passwordRealm, savePassword, user, pwd) {
  1807.         resource._authFailed = true;
  1808.         return false;
  1809.       },
  1810.       promptPassword: function(dialogTitle, text, passwordRealm, savePassword, pwd) {
  1811.         resource._authFailed = true;
  1812.         return false;
  1813.       }
  1814.     };
  1815.   },
  1816.  
  1817.   // nsIPrompt
  1818.  
  1819.   get prompt() {
  1820.     var resource = this;
  1821.     return {
  1822.       QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrompt]),
  1823.       alert: function(dialogTitle, text) {
  1824.         throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  1825.       },
  1826.       alertCheck: function(dialogTitle, text, checkMessage, checkValue) {
  1827.         throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  1828.       },
  1829.       confirm: function(dialogTitle, text) {
  1830.         throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  1831.       },
  1832.       confirmCheck: function(dialogTitle, text, checkMessage, checkValue) {
  1833.         throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  1834.       },
  1835.       confirmEx: function(dialogTitle, text, buttonFlags, button0Title, button1Title, button2Title, checkMsg, checkValue) {
  1836.         throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  1837.       },
  1838.       prompt: function(dialogTitle, text, value, checkMsg, checkValue) {
  1839.         throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  1840.       },
  1841.       promptPassword: function(dialogTitle, text, password, checkMsg, checkValue) {
  1842.         resource._authFailed = true;
  1843.         return false;
  1844.       },
  1845.       promptUsernameAndPassword: function(dialogTitle, text, username, password, checkMsg, checkValue) {
  1846.         resource._authFailed = true;
  1847.         return false;
  1848.       },
  1849.       select: function(dialogTitle, text, count, selectList, outSelection) {
  1850.         throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  1851.       }
  1852.     };
  1853.   },
  1854.  
  1855.   // XXX We implement nsIProgressEventSink because otherwise bug 253127
  1856.   // would cause too many extraneous errors to get reported to the console.
  1857.   // Fortunately this doesn't screw up XMLHttpRequest, because it ensures
  1858.   // that its implementation of nsIProgressEventSink will always get called
  1859.   // in addition to whatever notification callbacks we set on the channel.
  1860.  
  1861.   // nsIProgressEventSink
  1862.  
  1863.   onProgress: function(aRequest, aContext, aProgress, aProgressMax) {},
  1864.   onStatus: function(aRequest, aContext, aStatus, aStatusArg) {},
  1865.  
  1866.   /**
  1867.    * Initialize the resource from an existing DOM document object.
  1868.    * 
  1869.    * @param   document
  1870.    *          a DOM document object
  1871.    *
  1872.    */
  1873.   initFromDocument: function MSR_initFromDocument(document) {
  1874.     this._content = document;
  1875.     this._contentType = document.contentType;
  1876.  
  1877.     // Normally we set this property based on whether or not
  1878.     // XMLHttpRequest parsed the content into an XML document object,
  1879.     // but since we already have the content, we have to analyze
  1880.     // its content type ourselves to see if it is XML.
  1881.     this._isXML = (this.contentType == "text/xml" ||
  1882.                    this.contentType == "application/xml" ||
  1883.                    /^.+\/.+\+xml$/.test(this.contentType));
  1884.   },
  1885.  
  1886.   /**
  1887.    * Destroy references to avoid leak-causing cycles.  Instantiators must call
  1888.    * this method on all instances they instantiate once they're done with them.
  1889.    *
  1890.    */
  1891.   destroy: function MSR_destroy() {
  1892.     this._uri = null;
  1893.     this._content = null;
  1894.     this._loadCallback = null;
  1895.     this._errorCallback = null;
  1896.     this._loadTimer = null;
  1897.     this._authFailed = false;
  1898.     if (this._iframe) {
  1899.       if (this._iframe && this._iframe.parentNode)
  1900.         this._iframe.parentNode.removeChild(this._iframe);
  1901.       this._iframe = null;
  1902.     }
  1903.   },
  1904.  
  1905.   /**
  1906.    * Load the resource.
  1907.    * 
  1908.    * @param   loadCallback
  1909.    *          a function to invoke when the resource finishes loading
  1910.    * @param   errorCallback
  1911.    *          a function to invoke when an error occurs during the load
  1912.    *
  1913.    */
  1914.   load: function MSR_load(loadCallback, errorCallback) {
  1915.     LOG(this.uri.spec + " loading");
  1916.   
  1917.     this._loadCallback = loadCallback;
  1918.     this._errorCallback = errorCallback;
  1919.  
  1920.     var request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance();
  1921.   
  1922.     var loadHandler = {
  1923.       _self: this,
  1924.       handleEvent: function MSR_loadHandler_handleEvent(event) {
  1925.         if (this._self._loadTimer)
  1926.           this._self._loadTimer.cancel();
  1927.  
  1928.         this._self.status = event.target.status;
  1929.  
  1930.         if (this._self._authFailed || this._self.status >= 400) {
  1931.           // Technically the request succeeded, but we treat it as a failure,
  1932.           // since we won't be able to extract anything relevant from the result.
  1933.  
  1934.           // XXX For now HTTP is the only protocol we handle that might fail
  1935.           // auth. This message will need to change once we support FTP, which
  1936.           // returns 0 for all statuses.
  1937.           LOG(this._self.uri.spec + " load failed; HTTP status: " + this._self.status);
  1938.           try     { this._self._handleError(event) }
  1939.           finally { this._self = null }
  1940.         }
  1941.         else if (event.target.channel.contentType == "multipart/x-mixed-replace") {
  1942.           // Technically the request succeeded, but we treat it as a failure,
  1943.           // since we aren't able to handle multipart content.
  1944.           LOG(this._self.uri.spec + " load failed; contains multipart content");
  1945.           try     { this._self._handleError(event) }
  1946.           finally { this._self = null }
  1947.         }
  1948.         else {
  1949.           LOG(this._self.uri.spec + " load succeeded; invoking callback");
  1950.           try     { this._self._handleLoad(event) }
  1951.           finally { this._self = null }
  1952.         }
  1953.       }
  1954.     };
  1955.  
  1956.     var errorHandler = {
  1957.       _self: this,
  1958.       handleEvent: function MSR_errorHandler_handleEvent(event) {
  1959.         if (this._self._loadTimer)
  1960.           this._self._loadTimer.cancel();
  1961.  
  1962.         LOG(this._self.uri.spec + " load failed");
  1963.         try     { this._self._handleError(event) }
  1964.         finally { this._self = null }
  1965.       }
  1966.     };
  1967.  
  1968.     // cancel loads that take too long
  1969.     // timeout specified in seconds at browser.microsummary.requestTimeout,
  1970.     // or 300 seconds (five minutes)
  1971.     var timeout = getPref("browser.microsummary.requestTimeout", 300) * 1000;
  1972.     var timerObserver = {
  1973.       _self: this,
  1974.       observe: function MSR_timerObserver_observe() {
  1975.         LOG("timeout loading microsummary resource " + this._self.uri.spec + ", aborting request");
  1976.         request.abort();
  1977.         try     { this._self.destroy() }
  1978.         finally { this._self = null }
  1979.       }
  1980.     };
  1981.     this._loadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  1982.     this._loadTimer.init(timerObserver, timeout, Ci.nsITimer.TYPE_ONE_SHOT);
  1983.  
  1984.     request = request.QueryInterface(Ci.nsIDOMEventTarget);
  1985.     request.addEventListener("load", loadHandler, false);
  1986.     request.addEventListener("error", errorHandler, false);
  1987.     
  1988.     request = request.QueryInterface(Ci.nsIXMLHttpRequest);
  1989.     request.open(this.method, this.uri.spec, true);
  1990.     request.setRequestHeader("X-Moz", "microsummary");
  1991.     if (this.lastMod)
  1992.       request.setRequestHeader("If-Modified-Since", this.lastMod);
  1993.  
  1994.     // Register ourselves as a listener for notification callbacks so we
  1995.     // can handle authorization requests and SSL issues like cert mismatches.
  1996.     // XMLHttpRequest will handle the notifications we don't handle.
  1997.     request.channel.notificationCallbacks = this;
  1998.  
  1999.     // If this is a bookmarked resource, and the bookmarks service recorded
  2000.     // its charset in the bookmarks datastore the last time the user visited it,
  2001.     // then specify the charset in the channel so XMLHttpRequest loads
  2002.     // the resource correctly.
  2003.     try {
  2004.       var resolver = Cc["@mozilla.org/embeddor.implemented/bookmark-charset-resolver;1"].
  2005.                      getService(Ci.nsICharsetResolver);
  2006.       if (resolver) {
  2007.         var charset = resolver.requestCharset(null, request.channel, {}, {});
  2008.         if (charset != "")
  2009.           request.channel.contentCharset = charset;
  2010.       }
  2011.     }
  2012.     catch(ex) {}
  2013.  
  2014.     request.send(null);
  2015.   },
  2016.  
  2017.   _handleLoad: function MSR__handleLoad(event) {
  2018.     var request = event.target;
  2019.  
  2020.     if (request.responseXML) {
  2021.       this._isXML = true;
  2022.       // XXX Figure out the parsererror format and log a specific error.
  2023.       if (request.responseXML.documentElement.nodeName == "parsererror") {
  2024.         this._handleError(event);
  2025.         return;
  2026.       }
  2027.       this._content = request.responseXML;
  2028.       this._contentType = request.channel.contentType;
  2029.       this._loadCallback(this);
  2030.     }
  2031.  
  2032.     else if (request.channel.contentType == "text/html") {
  2033.       this._parse(request.responseText);
  2034.     }
  2035.  
  2036.     else {
  2037.       // This catches text/plain as well as any other content types
  2038.       // not accounted for by the content type-specific code above.
  2039.       this._content = request.responseText;
  2040.       this._contentType = request.channel.contentType;
  2041.       this._loadCallback(this);
  2042.     }
  2043.   },
  2044.  
  2045.   _handleError: function MSR__handleError(event) {
  2046.     // Call the error callback, then destroy ourselves to prevent memory leaks.
  2047.     try     { if (this._errorCallback) this._errorCallback(this) } 
  2048.     finally { this.destroy() }
  2049.   },
  2050.  
  2051.   /**
  2052.    * Parse a string of HTML text.  Used by _load() when it retrieves HTML.
  2053.    * We do this via hidden XUL iframes, which according to bz is the best way
  2054.    * to do it currently, since bug 102699 is hard to fix.
  2055.    * 
  2056.    * @param   htmlText
  2057.    *          a string containing the HTML content
  2058.    *
  2059.    */
  2060.   _parse: function MSR__parse(htmlText) {
  2061.     // Find a window to stick our hidden iframe into.
  2062.     var windowMediator = Cc['@mozilla.org/appshell/window-mediator;1'].
  2063.                          getService(Ci.nsIWindowMediator);
  2064.     var window = windowMediator.getMostRecentWindow("navigator:browser");
  2065.     // XXX We can use other windows, too, so perhaps we should try to get
  2066.     // some other window if there's no browser window open.  Perhaps we should
  2067.     // even prefer other windows, since there's less chance of any browser
  2068.     // window machinery like throbbers treating our load like one initiated
  2069.     // by the user.
  2070.     if (!window) {
  2071.       this._handleError(event);
  2072.       return;
  2073.     }
  2074.     var document = window.document;
  2075.     var rootElement = document.documentElement;
  2076.   
  2077.     // Create an iframe, make it hidden, and secure it against untrusted content.
  2078.     this._iframe = document.createElement('iframe');
  2079.     this._iframe.setAttribute("collapsed", true);
  2080.     this._iframe.setAttribute("type", "content");
  2081.   
  2082.     // Insert the iframe into the window, creating the doc shell.
  2083.     rootElement.appendChild(this._iframe);
  2084.  
  2085.     // When we insert the iframe into the window, it immediately starts loading
  2086.     // about:blank, which we don't need and could even hurt us (for example
  2087.     // by triggering bugs like bug 344305), so cancel that load.
  2088.     var webNav = this._iframe.docShell.QueryInterface(Ci.nsIWebNavigation);
  2089.     webNav.stop(Ci.nsIWebNavigation.STOP_NETWORK);
  2090.  
  2091.     // Turn off JavaScript and auth dialogs for security and other things
  2092.     // to reduce network load.
  2093.     // XXX We should also turn off CSS.
  2094.     this._iframe.docShell.allowJavascript = false;
  2095.     this._iframe.docShell.allowAuth = false;
  2096.     this._iframe.docShell.allowPlugins = false;
  2097.     this._iframe.docShell.allowMetaRedirects = false;
  2098.     this._iframe.docShell.allowSubframes = false;
  2099.     this._iframe.docShell.allowImages = false;
  2100.   
  2101.     var parseHandler = {
  2102.       _self: this,
  2103.       handleEvent: function MSR_parseHandler_handleEvent(event) {
  2104.         event.target.removeEventListener("DOMContentLoaded", this, false);
  2105.         try     { this._self._handleParse(event) }
  2106.         finally { this._self = null }
  2107.       }
  2108.     };
  2109.  
  2110.     // Convert the HTML text into an input stream.
  2111.     var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
  2112.                     createInstance(Ci.nsIScriptableUnicodeConverter);
  2113.     converter.charset = "UTF-8";
  2114.     var stream = converter.convertToInputStream(htmlText);
  2115.  
  2116.     // Set up a channel to load the input stream.
  2117.     var channel = Cc["@mozilla.org/network/input-stream-channel;1"].
  2118.                   createInstance(Ci.nsIInputStreamChannel);
  2119.     channel.setURI(this._uri);
  2120.     channel.contentStream = stream;
  2121.  
  2122.     // Load in the background so we don't trigger web progress listeners.
  2123.     var request = channel.QueryInterface(Ci.nsIRequest);
  2124.     request.loadFlags |= Ci.nsIRequest.LOAD_BACKGROUND;
  2125.  
  2126.     // Specify the content type since we're not loading content from a server,
  2127.     // so it won't get specified for us, and if we don't specify it ourselves,
  2128.     // then Firefox will prompt the user to download content of "unknown type".
  2129.     var baseChannel = channel.QueryInterface(Ci.nsIChannel);
  2130.     baseChannel.contentType = "text/html";
  2131.  
  2132.     // Load as UTF-8, which it'll always be, because XMLHttpRequest converts
  2133.     // the text (i.e. XMLHTTPRequest.responseText) from its original charset
  2134.     // to UTF-16, then the string input stream component converts it to UTF-8.
  2135.     baseChannel.contentCharset = "UTF-8";
  2136.  
  2137.     // Register the parse handler as a load event listener and start the load.
  2138.     // Listen for "DOMContentLoaded" instead of "load" because background loads
  2139.     // don't fire "load" events.
  2140.     this._iframe.addEventListener("DOMContentLoaded", parseHandler, true);
  2141.     var uriLoader = Cc["@mozilla.org/uriloader;1"].getService(Ci.nsIURILoader);
  2142.     uriLoader.openURI(channel, true, this._iframe.docShell);
  2143.   },
  2144.  
  2145.   /**
  2146.    * Handle a load event for the iframe-based parser.
  2147.    * 
  2148.    * @param   event
  2149.    *          the event object representing the load event
  2150.    *
  2151.    */
  2152.   _handleParse: function MSR__handleParse(event) {
  2153.     // XXX Make sure the parse was successful?
  2154.  
  2155.     this._content = this._iframe.contentDocument;
  2156.     this._contentType = this._iframe.contentDocument.contentType;
  2157.     this._loadCallback(this);
  2158.   }
  2159.  
  2160. };
  2161.  
  2162. /**
  2163.  * Get a resource currently loaded into a browser window.  Checks windows
  2164.  * one at a time, starting with the frontmost (a.k.a. most recent) one.
  2165.  * 
  2166.  * @param   uri
  2167.  *          the URI of the resource
  2168.  *
  2169.  * @returns a Resource object, if the resource is currently loaded
  2170.  *          into a browser window; otherwise null
  2171.  *
  2172.  */
  2173. function getLoadedMicrosummaryResource(uri) {
  2174.   var mediator = Cc["@mozilla.org/appshell/window-mediator;1"].
  2175.                  getService(Ci.nsIWindowMediator);
  2176.  
  2177.   // Apparently the Z order enumerator is broken on Linux per bug 156333.
  2178.   //var windows = mediator.getZOrderDOMWindowEnumerator("navigator:browser", true);
  2179.   var windows = mediator.getEnumerator("navigator:browser");
  2180.  
  2181.   while (windows.hasMoreElements()) {
  2182.     var win = windows.getNext();
  2183.     var tabBrowser = win.document.getElementById("content");
  2184.     for ( var i = 0; i < tabBrowser.browsers.length; i++ ) {
  2185.       var browser = tabBrowser.browsers[i];
  2186.       if (uri.equals(browser.currentURI)) {
  2187.         var resource = new MicrosummaryResource(uri);
  2188.         resource.initFromDocument(browser.contentDocument);
  2189.         return resource;
  2190.       }
  2191.     }
  2192.   }
  2193.  
  2194.   return null;
  2195. }
  2196.  
  2197. /**
  2198.  * Get a value from a pref or a default value if the pref doesn't exist.
  2199.  *
  2200.  * @param   prefName
  2201.  * @param   defaultValue
  2202.  * @returns the pref's value or the default (if it is missing)
  2203.  */
  2204. function getPref(prefName, defaultValue) {
  2205.   try {
  2206.     var prefBranch = Cc["@mozilla.org/preferences-service;1"].
  2207.                      getService(Ci.nsIPrefBranch);
  2208.     var type = prefBranch.getPrefType(prefName);
  2209.     switch (type) {
  2210.       case prefBranch.PREF_BOOL:
  2211.         return prefBranch.getBoolPref(prefName);
  2212.       case prefBranch.PREF_INT:
  2213.         return prefBranch.getIntPref(prefName);
  2214.     }
  2215.   }
  2216.   catch (ex) { /* return the default value */ }
  2217.   
  2218.   return defaultValue;
  2219. }
  2220.  
  2221.  
  2222. // From http://lxr.mozilla.org/mozilla/source/browser/components/search/nsSearchService.js
  2223.  
  2224. /**
  2225.  * Removes all characters not in the "chars" string from aName.
  2226.  *
  2227.  * @returns a sanitized name to be used as a filename, or a random name
  2228.  *          if a sanitized name cannot be obtained (if aName contains
  2229.  *          no valid characters).
  2230.  */
  2231. function sanitizeName(aName) {
  2232.   const chars = "-abcdefghijklmnopqrstuvwxyz0123456789";
  2233.   const maxLength = 60;
  2234.  
  2235.   var name = aName.toLowerCase();
  2236.   name = name.replace(/ /g, "-");
  2237.   //name = name.split("").filter(function (el) {
  2238.   //                               return chars.indexOf(el) != -1;
  2239.   //                             }).join("");
  2240.   var filteredName = "";
  2241.   for ( var i = 0 ; i < name.length ; i++ )
  2242.     if (chars.indexOf(name[i]) != -1)
  2243.       filteredName += name[i];
  2244.   name = filteredName;
  2245.  
  2246.   if (!name) {
  2247.     // Our input had no valid characters - use a random name
  2248.     for (var i = 0; i < 8; ++i)
  2249.       name += chars.charAt(Math.round(Math.random() * (chars.length - 1)));
  2250.   }
  2251.  
  2252.   if (name.length > maxLength)
  2253.     name = name.substring(0, maxLength);
  2254.  
  2255.   return name;
  2256. }
  2257.  
  2258. function NSGetModule(compMgr, fileSpec) {
  2259.   return XPCOMUtils.generateModule([MicrosummaryService]);
  2260. }
  2261.